/* (C) Copyright Nicolai M. Josuttis 2001.
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 * (C) Alex Besogonov
 * Cruelly hacked to use as a list-of-lists for JAM :)
 */
#ifndef JAM_STACK_ARRAY_HPP
#define JAM_STACK_ARRAY_HPP

#include <cstddef>
#include <stdexcept>
#include <boost/assert.hpp>
#include <boost/aligned_storage.hpp>

#include <algorithm>

//This class implements a circular buffer used for function arguments.
template<class T, std::size_t N>
class fixed_size_list
{
	union
	{
		char buf_[N*sizeof(T)];

		typename boost::type_with_alignment<
			boost::alignment_of<T>::value>::type align;
	} elems;
    size_t size_;
	size_t start_index_;

public:
    // type definitions. TODO: add iterators
    typedef T              value_type;
    typedef T&             reference;
    typedef const T&       const_reference;
    typedef std::size_t    size_type;
    typedef std::ptrdiff_t difference_type;

	fixed_size_list() : size_(), start_index_() {}
	fixed_size_list(const fixed_size_list<T,N> &other) : size_(), start_index_() 
	{
		operator = (other);
	}
	template<class Iter> fixed_size_list(Iter first,Iter end) : size_(), start_index_()
	{
		for(Iter f=first;f!=end;++f)
			push_back(*f);
	}
	fixed_size_list<T,N>& operator = (const fixed_size_list<T,N> &other)
	{
		if (&other==this) return *this;
		clear();
		for(size_t f=0,fend=other.size();f!=fend;++f)
			push_back(other[f]);
		return *this;
	}
	~fixed_size_list()
	{
		clear();
	}

    // operator[]
    reference operator[](size_type i) 
    { 
        BOOST_ASSERT( i < size_ && "out of range" ); 
        return *get_elem(i);
    }
    
    const_reference operator[](size_type i) const 
    {     
        BOOST_ASSERT( i < size_ && "out of range" ); 
        return *get_elem(i);
    }

    // at() with range check
    reference at(size_type i) { rangecheck(i); return *get_elem(i); }
    const_reference at(size_type i) const { rangecheck(i); return *get_elem(i); }

    // front() and back()
    reference front() 
    { 
        return *get_elem(0);
    }
    
    const_reference front() const 
    {
        return *get_elem(0);
    }
    
    reference back() 
    { 
        return *get_elem(size_-1);
    }
    
    const_reference back() const 
    { 
        return *get_elem(size_-1);
    }
    
    size_type size() const { return size_; }
    bool empty() const { return size_==0; }
    static size_type max_size() { return N; }
    enum { static_size = N };

	T& push_back(const T& elem)
	{
		if (size()==N) throw std::out_of_range("Static buffer size is too small");
		T& res=*new(static_cast<void*>(get_elem(size_))) T(elem);
		size_++;
		return res;
	}

	T& push_back()
	{
		if (size()==N) throw std::out_of_range("Static buffer size is too small");
		T& res=*new(static_cast<void*>(get_elem(size_))) T();
		size_++;
		return res;
	}

	void push_front(const T& elem)
	{
		if (size()==N) throw std::out_of_range("Static buffer size is too small");
		new(static_cast<void*>(get_elem(-1))) T(elem);
		size_++;
		start_index_--;
	}

	void push_front()
	{
		if (size()==N) throw std::out_of_range("Static buffer size is too small");
		new(static_cast<void*>(get_elem(-1))) T();
		size_++;
		start_index_--;
	}

	void pop_back()
	{
		assert(size_);
		get_elem(size_-1)->~T();
		--size_;
	}

	void pop_front()
	{
		assert(size_);
		get_elem(0)->~T();
		--size_;
		start_index_++;
	}

	void clear()
	{
		//Remove all elements
		for(int f=0,fsize=size();f!=fsize;f++)
			pop_back();
		assert(size_==0);
	}

    // check range (may be private because it is static)
    void rangecheck (size_type i) const
	{
        if (i >= size()) { 
            throw std::range_error("array<>: index out of range");
        }
    }

private:
	T* get_elem(size_t sz) const
	{
		size_t circle_idx=(sz+start_index_+N)%N;
		const char *pos=&elems.buf_[circle_idx*sizeof(T)];
		return reinterpret_cast<T*>(const_cast<char*>(pos));
	}
};

#endif //JAM_STACK_ARRAY_HPP
